package edu.kit.aifb.ldsbench.generator.opt2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.openrdf.model.URI;

import edu.kit.aifb.ldsbench.generator.opt.util2.SesameQuery;
import edu.kit.aifb.ldsbench.generator.util2.IDistributionValues;

class OptimizationSetter {

	/*
	 * Indicates if the properties links directly to an URI.
	 */
	static final byte DIRECT_LINK = 1;

	/*
	 * Indicates the crawling start point of a bnode property. 
	 */
	static final byte BNODE_START = 2;

	/*
	 * Indicates the crawling end point of a bnode property. 
	 */
	static final byte BNODE_DELIMITOR = 3;

	/*
	 * Indicates if the property was not set because of the estimation.
	 */
	static final byte UNSET_PROPERTY = 4;

	private static final int tries = 4;
	private static final double looseFactor = 0.05d;

	public static final double PROPERTY_VARIANCE = 0.1;
	public static final double COHERENCE_VARIANCE = 0.1d;
	public static final double SIZE_VARIANZE = 0.1d;


	private SesameQuery sesameQuery;

	private IDistributionValues distribValues;
	private IOptimizationSolver optSolver;
	private double coherence;
	private double linkageDegree;

	OptimizationSetter(IDistributionValues distribValues, 
			IOptimizationSolver optSolver, 
			SesameQuery sesameQuery,
			double coherence, double linkageDegree){

		this.distribValues = distribValues;
		this.sesameQuery = sesameQuery;
		this.coherence = coherence;
		this.optSolver = optSolver;
		this.linkageDegree = linkageDegree;
	}


	OptimizationResult setUpOptimizationProblem(double eventSize, Map<URI, List<URI>> generationInstances){

		int numInstances = 0;
		for(URI type : generationInstances.keySet()){
			numInstances += generationInstances.get(type).size();
		}
		OptimizationResult result = null;
		if(numInstances > 0){
			//decreasing the size by the num of instance defintions
			eventSize -= numInstances;

			ArrayList<Double> optimizationVariables = new ArrayList<Double>();
			ArrayList<Byte> linkageStructure = new ArrayList<Byte>();
			ArrayList<Integer> estimatedOccurence = new ArrayList<Integer>();
			ArrayList<Boolean> interdomain = new ArrayList<Boolean>();
			ArrayList<Double> coherence = new ArrayList<Double>();

			// looking for the properties which are part of the opt. problem
			findOptimizationVariables(optimizationVariables, linkageStructure, estimatedOccurence, generationInstances, 
					eventSize, interdomain, coherence);
			
			double coherenceSum = 0;
			for(double d : coherence){
				coherenceSum += d;
				System.out.print(d + ",");
			}
			System.out.println("coherenceSum: " + coherenceSum);
			
			if(optimizationVariables.size() > 0){
				//	System.out.println(optimizationVariables.size() + "=" + estimatedOccurence.size() +  "?");
				/*
				for(Integer b : estimatedOccurence){
					System.out.print(b + ", ");
				}
				 */
				double[] xopt;
				int round = 0;

				do {
					boolean minimize = false;
					double[] c = new double[optimizationVariables.size()];
					int i = 0;
					for(double entry : optimizationVariables){
						c[i++] = entry;
					}

					xopt = null;

					int dimA = c.length;

					double[] lb = new double[dimA];
					double[] ub = new double[dimA];

					if(round == 0){
						for(int j = 0; j  < dimA; j++){
							lb[j] = Math.max(0d , Math.floor(estimatedOccurence.get(j) - 1));
							ub[j] = Math.ceil(estimatedOccurence.get(j));
						}
					}else{
						for(int j = 0; j  < dimA; j++){
							lb[j] = Math.max(0d , Math.floor(estimatedOccurence.get(j) *  (1d - OptimizationSetter.PROPERTY_VARIANCE - round * looseFactor)));
							ub[j] = Math.ceil(estimatedOccurence.get(j) * (1d + OptimizationSetter.PROPERTY_VARIANCE + round * looseFactor));
						}
					}


					/* We have 4 constraints:
					 * 
					 * C1: sum(optvariables * impacts) < CH * (1 + CH_variance)
					 * C2: itd(x1) + itd(x2) + ... + itd(xn) < size * degreeOfLinkage
					 * 
					 * C3: (1 - sizeVariance) < sum(num(optvariables))
					 * C4: sum(num(optvariables)) < (1 + sizeVariance)
					 */

					double[] a = new double[4 * dimA];
					for(int j = 0; j < dimA; j++){
						// C1
						a[j] = coherence.get(j);
					}
					// C2 
					for(int j = dimA; j < 2 * dimA; j++){
						if(interdomain.get(j % dimA) == Boolean.TRUE){
							a[j] = 1d;
						} else {
							a[j] = 0d;
						}

					}
					// C3 + C4
					Arrays.fill(a, 2 * dimA, a.length, 1.0);

					// the type of the constraints
					byte[] ctype = new byte[4];
					double[] b = new double[4];
					// bound for C1
					b[0] = Math.min( this.coherence +  (round * looseFactor), 1d);
					System.out.println("coherence limit: " + b[0]);
					ctype[0] = OptProblemInstance.CONSTRAINT_WITH_UPPER_BOUND;
					// bound for C2
					// max 1 to prevent scenarios with no linkage
					System.out.println("linkage: " + this.linkageDegree + " eventSize: " + eventSize + " *= " + this.linkageDegree * eventSize);
					b[1] = Math.max(Math.round((this.linkageDegree * optimizationVariables.size() + round * looseFactor)), 0d);
					ctype[1] = OptProblemInstance.CONSTRAINT_WITH_UPPER_BOUND;
					// bound for C3
					b[2] = 0;// (1d - OptimizationSetter.SIZE_VARIANZE - round * looseFactor) * eventSize;
					ctype[2] = OptProblemInstance.CONSTRAINT_WITH_LOWER_BOUND;
					// bound for C4
					b[3] = (1d + OptimizationSetter.SIZE_VARIANZE + round * looseFactor) * eventSize;
					ctype[3] = OptProblemInstance.CONSTRAINT_WITH_UPPER_BOUND;

					/*
			if(optimizationVariables.size() == 0){
				System.out.println("dimension = 0");
			}

			for(int z = 0; z < b.length; z++){
				System.out.print(" ctype[" + z + "] = " + ctype[z]);
			}
			System.out.println("");
			for(int z = 0; z < b.length; z++){
				System.out.print(" b[" + z + "] = " + b[z]);
			}

			System.out.println("");
			for(int z = 0; z < dimA; z++){
				System.out.print(" lb [" + z + "] = " + lb[z]);
			}
			System.out.println("");
			for(int z = 0; z < dimA; z++){
				System.out.print(" ub[" + z + "] = " + ub[z]);
			}

			System.out.println("");

			for(int z = 0 ; z < a.length; z++){
				if((z % dimA )== 0){
					System.out.println("");
				}
				System.out.print(" a[" + z + "] = " + a[z]);

			}

			System.out.println("Sending opt problem");
					 */
					OptProblemInstance opi = new OptProblemInstance(a, ctype, dimA, c, b, lb, ub, minimize);
					System.out.println("opt problem ready");
					xopt = this.optSolver.sendOptimizationProblem(opi);
					System.out.println("solution received");
					round++;
				} while(xopt == null && round < tries);
				/*
		for(Byte ls : linkageStructure){
			System.out.print(ls + ", ");
		}
				 */
				//System.out.print("  [num: " + linkageStructure.size() + "]");
				//System.out.println("");

				/*
		xopt = new double[optimizationVariables.size()];
		for(int z = 0; z < xopt.length; z++){
			xopt[z] = 1;
		}
				 */
				if(xopt != null){
					System.out.println("round: " +round);
					result = new OptimizationResult(xopt, linkageStructure);
				}
			}
		}
		return result;
	}


	private void findOptimizationVariables(ArrayList<Double> optimizationVariables, 
			ArrayList<Byte> linkageStructure,
			ArrayList<Integer> estimatedOccurence,
			Map<URI, List<URI>> generationInstances,
			double eventSize,
			ArrayList<Boolean> interdomain,
			ArrayList<Double> coherence){

		double impact;
		double coverageWeight;
		double weightDenominator = 0d;

		for(URI type : generationInstances.keySet()){
			// denominator = for_all_types (|I| + |P| )
			if(generationInstances.get(type).size() > 0){
				//only add to calculation when it occurs
				weightDenominator += generationInstances.get(type).size() + this.sesameQuery.numProperties(type);
			}
		}
		for(URI type : generationInstances.keySet()){
			// getting the properties the instances can have
			for(int i = 0; i < generationInstances.get(type).size(); i++){
				//  WT(T)	 = 	|P| + |I| / sum_over_all_types_t ( | P(t) | + |I(T|  )
				coverageWeight = (this.sesameQuery.numProperties(type) + generationInstances.get(type).size()) / (weightDenominator);
				// impact = coverageWeight * 1 / |P| * |I|
				impact = coverageWeight * (1d / (generationInstances.get(type).size() * this.sesameQuery.numProperties(type)) );
				findOptimizationVariables(type, impact, 
						optimizationVariables, linkageStructure, 
						estimatedOccurence, type, eventSize,
						interdomain, coherence);
			}
		}
	}

	private void findOptimizationVariables(URI instanceType, double impact,
			ArrayList<Double> optimizationVariables,
			ArrayList<Byte> linkageStructure,
			ArrayList<Integer> estimatedOccurence,
			URI subjectType, double eventSize,
			ArrayList<Boolean> interdomain,
			ArrayList<Double> coherence){

		ArrayList<URI> currentRange;
		//	System.out.println(subjectType.stringValue());
		Set<URI> currentProperties = this.sesameQuery.getOutgoingProperties(subjectType);
		//	System.out.println("has domain attributes: " + currentProperties.size());

		// every instance need to set its optimization problem for each property according to their distribution
		for(URI property : currentProperties){
			//	System.out.println(property.stringValue());
			// if the property is unknown (because it did not occur in the listened data) we ignore it.

			if(this.sesameQuery.typeHasProperty(instanceType, property)){

				if(this.sesameQuery.isBNodeProperty(property, instanceType)){
					/*
					 * The property links to a bnode we need to crawl further
					 */
					// we need to get the type of the bnode 
					currentRange = this.sesameQuery.getRangeOfProperty(property);
					for(URI bnodeType : currentRange){
						//	System.out.println(bnodeType.stringValue());
						//	System.out.println("bnodetype: " + bnodeType.stringValue());
						linkageStructure.add(OptimizationSetter.BNODE_START);
						findOptimizationVariables(instanceType, impact, 
								optimizationVariables, linkageStructure, 
								estimatedOccurence, bnodeType, eventSize, 
								interdomain, coherence);
						linkageStructure.add(OptimizationSetter.BNODE_DELIMITOR);
					}
				}else{
					// the property links to a normal URI. We need to formulate the optimization problem with this variable.
					double num =  this.distribValues.getDistributionValue(this.sesameQuery.getDistribution(property, instanceType, SesameQuery.PROPERTY_DISTRIB));
					// if the estimation says that 0 will occur we do not add them to the opt problem
					//	System.out.println("directlink: " + property.stringValue());
					if(num > 0){
						// the result of the distributions are needed later to build the constraints
						estimatedOccurence.add((int) num);
						if(this.sesameQuery.isInterDomain(property, instanceType)){
							optimizationVariables.add( (1d / eventSize) + (impact / num));
							coherence.add(impact / num);
							interdomain.add(Boolean.TRUE);
							//System.out.println(property.stringValue());
							//System.out.println(property.getNamespace());
						}else{
							interdomain.add(Boolean.FALSE);
							optimizationVariables.add( impact / num  );
							coherence.add(impact / num);
						}
						linkageStructure.add(OptimizationSetter.DIRECT_LINK);
						//	System.out.println("directlink: " + property.stringValue());
					}else{
						linkageStructure.add(OptimizationSetter.UNSET_PROPERTY);
					}
				}
			}
		}
	}

}
